﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Reflection;
using Bymed.SpineScan.DataModel;
using Bymed.SpineScan.Repository.Interface;

namespace Bymed.SpineScan.Service.Shared
{
    public static class DataSetHelper
    {
        /// <summary>
        /// Create the DataSet structure based on current data source.
        /// </summary>
        /// <returns>The DataSet structure based on current data source.</returns>
        public static DataSet CreateCurrentDataSourceDataSet()
        {
            var dataSet = new DataSet();
            dataSet.Tables.Add(CreateDataTable<AttendingDoctor>());
            dataSet.Tables.Add(CreateDataTable<DiagnosticMessageReference>());
            dataSet.Tables.Add(CreateDataTable<Patient>());
            dataSet.Tables.Add(CreateDataTable<TestTracking>());
            dataSet.Tables.Add(CreateDataTable<TestResult>());
            dataSet.Tables.Add(CreateDataTable<TestResultDiagnosticMessage>());

            return dataSet;
        }

        /// <summary>
        /// Create DataTable struct based on provided DataModel. 
        /// Any ICollection, IList, IEnumerable & IDictionary property will be ignored.
        /// </summary>
        /// <typeparam name="T">The type of DataModel.</typeparam>
        /// <returns>The DataTable struct based on provide DataModel.</returns>
        public static DataTable CreateDataTable<T>() where T : class
        {
            var dataTable = new DataTable();
            dataTable.TableName = (typeof(T)).Name;
            var properties = (typeof(T)).GetProperties();

            foreach (var property in properties)
            {
                if (CheckAcceptableDataType(property))
                {
                    dataTable.Columns.Add(new DataColumn(property.Name, GetPropertyType(property)));
                }
            }

            return dataTable;
        }

        /// <summary>
        /// Create DataTable struct based on provided DataModel and fill the data into it by using provided repository.
        /// </summary>
        /// <typeparam name="T">The type of DataModel.</typeparam>
        /// <param name="repository">The instance that implements IRespository.</param>
        /// <returns>The DataTable full of the data based on provide DataModel</returns>
        public static DataTable CreateFillDataTable<T>(IRepository<T> repository) where T : class
        {
            var dataTable = CreateDataTable<T>();
            var allRows = repository.GetAll();

            foreach (var row in allRows)
            {
                var dataType = row.GetType();
                var dataRow = dataTable.NewRow();

                foreach (var property in dataType.GetProperties())
                {
                    if (CheckAcceptableDataType(property))
                    {
                        var propertyValue = property.GetValue(row, null);
                        dataRow[property.Name] = propertyValue == null ? DBNull.Value : propertyValue;
                    }
                }

                dataTable.Rows.Add(dataRow);
            }

            return dataTable;
        }

        /// <summary>
        /// Transfer the data from provided DataTable to List<T>
        /// </summary>
        /// <typeparam name="T">The data model type.</typeparam>
        /// <param name="dataTable">The DataTable Contains all data.</param>
        /// <returns>IList<T></returns>
        public static IList<T> TransferDataFromDataTableToDataModel<T>(DataTable dataTable) where T : class
        {
            var dataList = new List<T>();
            var dataModelType = typeof(T);

            foreach (DataRow row in dataTable.Rows)
            {
                var dataModel = Activator.CreateInstance<T>();

                foreach (var property in dataModelType.GetProperties())
                {
                    if (CheckAcceptableDataType(property))
                    {
                        try
                        {
                            if (row[property.Name] == DBNull.Value)
                            {
                                property.SetValue(dataModel, null);
                            }
                            else
                            {
                                property.SetValue(dataModel, Convert.ChangeType(row[property.Name], GetPropertyType(property)));
                            }
                        }
                        catch (ArgumentException)
                        {
                            // If one property contains null then DataTable might not contain this column since the data value is null.
                            // Try to access this column will cause ArgumentException.
                            property.SetValue(dataModel, null);
                        }
                    }
                }

                dataList.Add(dataModel);
            }

            return dataList;
        }

        /// <summary>
        /// Check if the property is acceptable for writing to xml file.
        /// </summary>
        /// <param name="property">The instance of PropertyInfo.</param>
        /// <returns>True means this property is acceptable for writing to xml file. False means NOT.</returns>
        private static bool CheckAcceptableDataType(PropertyInfo property)
        {
            var propertyFullName = property.PropertyType.FullName.ToLower();

            if (!propertyFullName.Contains("icollection") &&
                !propertyFullName.Contains("ilist") &&
                !propertyFullName.Contains("ienumerable") &&
                !propertyFullName.Contains("idictionary") &&
                !propertyFullName.Contains("datamodel") &&
                !property.Name.ToLower().Equals("id"))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Get the property type. If the property is based on Generic then the GenericType will be returned.
        /// </summary>
        /// <param name="property">The provided property.</param>
        /// <returns>The PropertyType.</returns>
        private static Type GetPropertyType(PropertyInfo property)
        {
            return property.PropertyType.GenericTypeArguments.Count() > 0 ? property.PropertyType.GenericTypeArguments[0] : property.PropertyType;
        }
    }
}